﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Collections.ObjectModel;
using System.ServiceModel.Activation;
using System.Threading;
using System.Xml;
using System.IO;
using Moq;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.Serialization.Formatters.Binary;
using Airport.AirportDataTypes;

namespace Airport
{    
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class AirportService : IAirportService
    {
        // Length of Runway 24L ~2675 m
        // Length of Runway 24L in pixels ~516.9768 px
        // Airport Map Scale Factor in pixels/m
        private double _mapScaleFactor = 5.174;

        private string[] _runwayNames = { "6L/24R", "6R/24L", "7L/25R", "7R/25L" };

        // Airport Data Binary File Names
        private string _airportMapBinaryFileName = "AirportMap.bin";
        private string _airportNodesBinaryFileName = "AirportNodes.bin";
        private string _airportEdgesBinaryFileName = "AirportEdges.bin";

        // Set this to true to use predefined test data
        private bool _useTestData = true;
        // Test data structures
        private List<Route> testRoutes = new List<Route>();

        public static Dictionary<IAirportCallback, IAirportCallback> _clients =
            new Dictionary<IAirportCallback, IAirportCallback>();

        public static Dictionary<IAirportCallback, IAirportCallback> _planeUpdateClients =
           new Dictionary<IAirportCallback, IAirportCallback>();

        public static Dictionary<IAirportCallback, IAirportCallback> _incursionDetectedClients =
           new Dictionary<IAirportCallback, IAirportCallback>();

        // SuggestionEngine Reference
        private SuggestionEngine _suggestionEngine;

        // EnvironmentUpdater Reference
        private EnvironmentUpdater _environmentUpdater;

        // Airport environment data structures
        private Map _airportMap;
        private List<Node> _airportNodes;
        private List<Edge> _airportEdges;
        private List<Plane> _activePlanes;
        public Object _activePlanesLock = new Object();
        private List<Flight> _activeFlights;
        private List<Scenario> _airportScenarios;
        private Dictionary<string, Node> _surfaceNameToNode;

        // This allows for mocking
        private OperationContext _operationContext;

        // Global counter for Incursions
        private int _currentIncursionId = 0;
        private List<Incursion> _incursionList = new List<Incursion>();

        public AirportService()
            : this(OperationContext.Current, LoadAirportMapFromFile(Path.GetFullPath(
                    Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "airport.xml"))))
        {
        }

        public AirportService(OperationContext currentOperationContext, Map airportMap)
        {
            lock (_activePlanesLock)
            {
                _activePlanes = new List<Plane>();
            }
            _activeFlights = new List<Flight>();
            _airportNodes = new List<Node>();
            _airportEdges = new List<Edge>();
            _airportScenarios = new List<Scenario>();

            _operationContext = currentOperationContext;
            
            // Load airport map from XML file
            //_airportMap = airportMap;                
            IFormatter formatter = new BinaryFormatter();
                
            // Load Airport Map from Binary
            /*
            Stream stream = new FileStream(Path.GetFullPath(
                Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, _airportMapBinaryFileName)), 
                FileMode.Open, FileAccess.Read, FileShare.Read);
            _airportMap = (Map)formatter.Deserialize(stream);
            stream.Close();
            */

            // Load Airport Nodes from Binary
            Stream stream = new FileStream(Path.GetFullPath(
                Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, _airportNodesBinaryFileName)),
                FileMode.Open, FileAccess.Read, FileShare.Read);
            _airportNodes = (List<Node>)formatter.Deserialize(stream);
            stream.Close();

            // Load Airport Edges from Binary
            stream = new FileStream(Path.GetFullPath(
                Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, _airportEdgesBinaryFileName)),
                FileMode.Open, FileAccess.Read, FileShare.Read);
            _airportEdges = (List<Edge>)formatter.Deserialize(stream);
            stream.Close();

            // Initialize airport nodes and edges
            InitializeAirportNodesAndEdges();
            InitializeSurfaceNameLists();

            if (_useTestData)
            {
                SetupInitialConditions();
            }

            _suggestionEngine = new SuggestionEngine(this);
            _environmentUpdater = new EnvironmentUpdater(this);
            
            new Thread(new ThreadStart(IncursionDetectorThread))
            {
                IsBackground = true,
                Name = "Incursion Detector Thread"
            }
            .Start();
            
        }

        private void InitializeSurfaceNameLists()
        {
            _surfaceNameToNode = new Dictionary<string, Node>();

            Node runwayNode = new Node(624, 83);
            _surfaceNameToNode.Add("24L", runwayNode);
            //_nodeToSurfaceName.Add(runwayNode, "24L");

            runwayNode = new Node(618, 42);
            _surfaceNameToNode.Add("24R", runwayNode);
            //_nodeToSurfaceName.Add(runwayNode, "24R");

            runwayNode = new Node(959, 359);
            _surfaceNameToNode.Add("25L", runwayNode);
            //_nodeToSurfaceName.Add(runwayNode, "25L");

            runwayNode = new Node(1012, 306);
            _surfaceNameToNode.Add("25R", runwayNode);
            //_nodeToSurfaceName.Add(runwayNode, "25R");

            runwayNode = new Node(105, 106);
            _surfaceNameToNode.Add("6L", runwayNode);
            //_nodeToSurfaceName.Add(runwayNode, "6L");

            runwayNode = new Node(38, 154);
            _surfaceNameToNode.Add("6R", runwayNode);
            //_nodeToSurfaceName.Add(runwayNode, "6R");

            runwayNode = new Node(317, 390);
            _surfaceNameToNode.Add("7L", runwayNode);
            //_nodeToSurfaceName.Add(runwayNode, "7L");

            runwayNode = new Node(323, 437);
            _surfaceNameToNode.Add("7R", runwayNode);
            //_nodeToSurfaceName.Add(runwayNode, "7R");
        }

        /// <summary>
        /// Plane to set up the initial conditions of the Airport. Call this before instantiating 
        /// SuggestionEngine or EnvironmentUpdater.
        /// </summary>
        private void SetupInitialConditions()
        {   
            // Set this to the number of test planes to create
            int numTestPlanes = 13;

            List<Plane> testPlanes = new List<Plane>();
            int[] testHeadings = new int[numTestPlanes];
            Dictionary<string, Node> initialPlaneLocations = new Dictionary<string, Node>();
            Dictionary<string, Node> initialPreviousMapNodes = new Dictionary<string, Node>();
            Dictionary<string, WorldVector> initialPlanePositionVectors = new Dictionary<string, WorldVector>();
            Dictionary<string, Edge> initialPlaneEdgeLocations = new Dictionary<string, Edge>();
            Flight[] testFlights = new Flight[numTestPlanes];
            string[] testFlightNames = new string[numTestPlanes];
            List<string> surfaceNames = new List<string>();

            // Airline Codes
            testFlightNames[0] = "ASA222";
            testFlightNames[1] = "COA503";
            testFlightNames[2] = "AAL385"; // Difficult to pronounce
            testFlightNames[3] = "NZ3306";
            testFlightNames[4] = "VX89";
            testFlightNames[5] = "UA3727";
            testFlightNames[6] = "AS6676";
            testFlightNames[7] = "VX114";
            testFlightNames[8] = "FX1801";
            testFlightNames[9] = "NZ5";
            testFlightNames[10] = "AC6092";
            testFlightNames[11] = "COA4895";
            testFlightNames[12] = "LH7164"; // Difficult to pronounce

            // Runway and Taxiway Names
            surfaceNames.Add("C10"); // 0
            surfaceNames.Add("C9"); // 1
            surfaceNames.Add("C8"); // 2
            surfaceNames.Add("C7"); // 3
            surfaceNames.Add("C6"); // 4
            surfaceNames.Add("D10"); // 5
            surfaceNames.Add("D9"); // 6
            surfaceNames.Add("D8"); // 7
            surfaceNames.Add("D7"); // 8
            surfaceNames.Add("A6"); // 9
            surfaceNames.Add("A5"); // 10
            surfaceNames.Add("A2"); // 11
            surfaceNames.Add("A1"); // 12
            surfaceNames.Add("6L"); // 13
            surfaceNames.Add("24R"); // 14
            surfaceNames.Add("6R"); // 15
            surfaceNames.Add("24L"); // 16
            surfaceNames.Add("7L"); // 17
            surfaceNames.Add("25R"); // 18
            surfaceNames.Add("7R"); // 19
            surfaceNames.Add("25L"); // 20
            surfaceNames.Add("24R@Z"); // 21

            // Plane Headings
            for (int i = 0; i < numTestPlanes; i++)
            {
                testHeadings[i] = 220;
            }
            
            // Initial Plane Locations
            initialPlaneLocations.Add("C10", new Node(-1, 512, 271)); // Beginning of Taxiway C10            
            initialPlaneLocations.Add("C9", new Node(-1, 556, 265)); // Beginning of Taxiway C9            
            initialPlaneLocations.Add("C8", new Node(-1, 598, 260)); // Beginning of Taxiway C8
            initialPlaneLocations.Add("C7", new Node(-1, 640, 257)); // Beginning of Taxiway C7
            initialPlaneLocations.Add("C6", new Node(-1, 684, 250)); // Beginning of Taxiway C6                      
            initialPlaneLocations.Add("D10", new Node(-1, 499, 188)); // Beginning of Taxiway D10
            initialPlaneLocations.Add("D9", new Node(-1, 557, 181)); // Beginning of Taxiway D9
            initialPlaneLocations.Add("D8", new Node(-1, 616, 174)); // Beginning of Taxiway D8
            initialPlaneLocations.Add("D7", new Node(-1, 658, 165)); // Beginning of Taxiway D7
            initialPlaneLocations.Add("A6", new Node(-1, 691, 436)); // Beginning of Taxiway A6
            initialPlaneLocations.Add("A5", new Node(-1, 768, 424)); // Beginning of Taxiway A5
            initialPlaneLocations.Add("A2", new Node(-1, 922, 441)); // Beginning of Taxiway A2
            initialPlaneLocations.Add("A1", new Node(-1, 992, 410)); // Beginning of Taxiway A1
            initialPlaneLocations.Add("6L", new Node(-1, 105, 106)); // Beginning of Runway 6L
            initialPlaneLocations.Add("24R", new Node(-1, 618, 42)); // Beginning of Runway 24R
            initialPlaneLocations.Add("6R", new Node(-1, 38, 154)); // Beginning of Runway 6R
            initialPlaneLocations.Add("24L", new Node(-1, 624, 83)); // Beginning of Runway 24L
            initialPlaneLocations.Add("7L", new Node(-1, 317, 390)); // Beginning of Runway 7L
            initialPlaneLocations.Add("25R", new Node(-1, 1012, 306)); // Beginning of Runway 25R
            initialPlaneLocations.Add("7R", new Node(-1, 323, 437)); // Beginning of Runway 7R
            initialPlaneLocations.Add("25L", new Node(-1, 959, 359)); // Beginning of Runway 25L
            initialPlaneLocations.Add(surfaceNames[21], new Node(333, 85)); // Runway 25R @ Taxiway Z

            initialPlaneEdgeLocations.Add("C10", new Edge(initialPlaneLocations["C10"], new Node(-1, 516, 326)));
            initialPlaneEdgeLocations.Add("C9", new Edge(initialPlaneLocations["C9"], new Node(-1, 560, 321)));
            initialPlaneEdgeLocations.Add("C8", new Edge(initialPlaneLocations["C8"], new Node(-1, 604, 316)));
            initialPlaneEdgeLocations.Add("C7", new Edge(initialPlaneLocations["C7"], new Node(-1, 648, 311)));
            initialPlaneEdgeLocations.Add("C6", new Edge(initialPlaneLocations["C6"], new Node(-1, 692, 306)));
            initialPlaneEdgeLocations.Add("D10", new Edge(initialPlaneLocations["D10"], new Node(-1, 495, 139)));
            initialPlaneEdgeLocations.Add("D9", new Edge(initialPlaneLocations["D9"], new Node(-1, 551, 133)));
            initialPlaneEdgeLocations.Add("D8", new Edge(initialPlaneLocations["D8"], new Node(-1, 608, 125)));
            initialPlaneEdgeLocations.Add("D7", new Edge(initialPlaneLocations["D7"], new Node(-1, 653, 120)));
            initialPlaneEdgeLocations.Add("A6", new Edge(initialPlaneLocations["A6"], new Node(-1, 689, 417)));
            initialPlaneEdgeLocations.Add("A5", new Edge(initialPlaneLocations["A5"], new Node(-1, 769, 407)));
            initialPlaneEdgeLocations.Add("A2", new Edge(initialPlaneLocations["A2"], new Node(-1, 923, 388)));
            initialPlaneEdgeLocations.Add("A1", new Edge(initialPlaneLocations["A1"], new Node(-1, 992, 392)));
            initialPlaneEdgeLocations.Add("6L", new Edge(initialPlaneLocations["6L"], new Node(-1, 217, 92)));
            initialPlaneEdgeLocations.Add("24R", new Edge(initialPlaneLocations["24R"], new Node(-1, 459, 62)));
            initialPlaneEdgeLocations.Add("6R", new Edge(initialPlaneLocations["6R"], new Node(-1, 109, 145)));
            initialPlaneEdgeLocations.Add("24L", new Edge(initialPlaneLocations["24L"], new Node(-1, 604, 85)));
            initialPlaneEdgeLocations.Add("7L", new Edge(initialPlaneLocations["7L"], new Node(-1, 431, 376)));
            initialPlaneEdgeLocations.Add("25R", new Edge(initialPlaneLocations["25R"], new Node(-1, 955, 313)));
            initialPlaneEdgeLocations.Add("7R", new Edge(initialPlaneLocations["7R"], new Node(-1, 437, 423)));
            initialPlaneEdgeLocations.Add("25L", new Edge(initialPlaneLocations["25L"], new Node(-1, 815, 377)));
            initialPlaneEdgeLocations.Add(surfaceNames[21], new Edge(initialPlaneLocations[surfaceNames[21]], new Node(308, 106)));

            // Initial PreviousMapNodes
            initialPreviousMapNodes.Add("C10", new Node(-1, 512, 271)); // Beginning of Taxiway C10            
            initialPreviousMapNodes.Add("C9", new Node(-1, 556, 265)); // Beginning of Taxiway C9            
            initialPreviousMapNodes.Add("C8", new Node(-1, 598, 260)); // Beginning of Taxiway C8
            initialPreviousMapNodes.Add("C7", new Node(-1, 640, 257)); // Beginning of Taxiway C7
            initialPreviousMapNodes.Add("C6", new Node(-1, 684, 250)); // Beginning of Taxiway C6                      
            initialPreviousMapNodes.Add("D10", new Node(-1, 499, 188)); // Beginning of Taxiway D10
            initialPreviousMapNodes.Add("D9", new Node(-1, 557, 181)); // Beginning of Taxiway D9
            initialPreviousMapNodes.Add("D8", new Node(-1, 616, 174)); // Beginning of Taxiway D8
            initialPreviousMapNodes.Add("D7", new Node(-1, 658, 165)); // Beginning of Taxiway D7
            initialPreviousMapNodes.Add("A6", new Node(-1, 691, 436)); // Beginning of Taxiway A6
            initialPreviousMapNodes.Add("A5", new Node(-1, 768, 424)); // Beginning of Taxiway A5
            initialPreviousMapNodes.Add("A2", new Node(-1, 922, 441)); // Beginning of Taxiway A2
            initialPreviousMapNodes.Add("A1", new Node(-1, 992, 410)); // Beginning of Taxiway A1
            initialPreviousMapNodes.Add("6L", new Node(-1, 105, 106)); // Beginning of Runway 6L
            initialPreviousMapNodes.Add("24R", new Node(-1, 618, 42)); // Beginning of Runway 24R
            initialPreviousMapNodes.Add("6R", new Node(-1, 38, 154)); // Beginning of Runway 6R
            initialPreviousMapNodes.Add("24L", new Node(-1, 624, 83)); // Beginning of Runway 24L
            initialPreviousMapNodes.Add("7L", new Node(-1, 317, 390)); // Beginning of Runway 7L
            initialPreviousMapNodes.Add("25R", new Node(-1, 1012, 306)); // Beginning of Runway 25R
            initialPreviousMapNodes.Add("7R", new Node(-1, 323, 437)); // Beginning of Runway 7R
            initialPreviousMapNodes.Add("25L", new Node(-1, 959, 359)); // Beginning of Runway 25L
            initialPreviousMapNodes.Add(surfaceNames[21], initialPlaneLocations[surfaceNames[21]]);

            // Initial Plane Position Vectors
            initialPlanePositionVectors.Add("C10", new WorldVector(512, 271, 0)); // Beginning of Taxiway C10            
            initialPlanePositionVectors.Add("C9", new WorldVector(556, 265, 0)); // Beginning of Taxiway C9            
            initialPlanePositionVectors.Add("C8", new WorldVector(598, 260, 0)); // Beginning of Taxiway C8
            initialPlanePositionVectors.Add("C7", new WorldVector(640, 257, 0)); // Beginning of Taxiway C7
            initialPlanePositionVectors.Add("C6", new WorldVector(684, 250, 0)); // Beginning of Taxiway C6                      
            initialPlanePositionVectors.Add("D10", new WorldVector(499, 188, 0)); // Beginning of Taxiway D10
            initialPlanePositionVectors.Add("D9", new WorldVector(557, 181, 0)); // Beginning of Taxiway D9
            initialPlanePositionVectors.Add("D8", new WorldVector(616, 174, 0)); // Beginning of Taxiway D8
            initialPlanePositionVectors.Add("D7", new WorldVector(658, 165, 0)); // Beginning of Taxiway D7
            initialPlanePositionVectors.Add("A6", new WorldVector(691, 436, 0)); // Beginning of Taxiway A6
            initialPlanePositionVectors.Add("A5", new WorldVector(768, 424, 0)); // Beginning of Taxiway A5
            initialPlanePositionVectors.Add("A2", new WorldVector(922, 441, 0)); // Beginning of Taxiway A2
            initialPlanePositionVectors.Add("A1", new WorldVector(992, 410, 0)); // Beginning of Taxiway A1
            initialPlanePositionVectors.Add("6L", new WorldVector(105, 106, 0)); // Beginning of Runway 6L
            initialPlanePositionVectors.Add("24R", new WorldVector(618, 42, 0)); // Beginning of Runway 24R
            initialPlanePositionVectors.Add("6R", new WorldVector(38, 154, 0)); // Beginning of Runway 6R
            initialPlanePositionVectors.Add("24L", new WorldVector(624, 83, 0)); // Beginning of Runway 24L
            initialPlanePositionVectors.Add("7L", new WorldVector(317, 390, 0)); // Beginning of Runway 7L
            initialPlanePositionVectors.Add("25R", new WorldVector(1012, 306, 0)); // Beginning of Runway 25R
            initialPlanePositionVectors.Add("7R", new WorldVector(323, 437, 0)); // Beginning of Runway 7R
            initialPlanePositionVectors.Add("25L", new WorldVector(959, 359, 0)); // Beginning of Runway 25L
            initialPlanePositionVectors.Add(surfaceNames[21], new WorldVector(initialPlaneLocations[surfaceNames[21]]));

            for (int i = 0; i < surfaceNames.Count; i++)
            {
                var actualEdgeQuery = from airportEdge in _airportEdges
                                      where airportEdge == initialPlaneEdgeLocations[surfaceNames[i]]
                                      select airportEdge;
                initialPlaneEdgeLocations[surfaceNames[i]].Name = actualEdgeQuery.First().Name;
            }
            
            /*
            for (int i = 0; i < numTestPlanes; i++)
            {
                testFlights[i] = new Flight();
                testFlights[i].FlightId = i;
                testFlights[i].FlightName = testFlightNames[i];

                Plane testPlane = new Plane();
                testPlane.PlaneId = i;
                testPlane.Heading = testHeadings[i];
                testPlane.Location = initialPlaneLocations[i];
                testPlane.PositionVector = initialPlanePositionVectors[i];
                testPlane.CurrentEdge = initialPlaneEdgeLocations[i];
                testPlane.PreviousMapNode = initialPreviousMapNodes[i];
                testPlane.PlaneFlight = testFlights[i];

                testPlanes.Add(testPlane);
                //_activePlanes.Add(testPlanes[i]);
                //_activeFlights.Add(testFlights[i]);
            }
            
            Scenario newScenario = new Scenario(0);
            newScenario.ScenarioPlanes.AddRange(testPlanes);

            _airportScenarios.Add(newScenario);
            */

            Plane plane0, plane1;
            // Scenario 0
            // Plane 0 at A6
            // Plane 1 at beginning of 25R
            testPlanes = new List<Plane>();
            plane0 = new Plane();
            plane0.PlaneId = 0;
            plane0.Heading = 265;
            plane0.Location = initialPlaneLocations["24R"];
            plane0.PositionVector = initialPlanePositionVectors["24R"];
            plane0.CurrentEdge = initialPlaneEdgeLocations["24R"];
            plane0.PreviousMapNode = initialPreviousMapNodes["24R"];
            plane0.DestinationGate = "D10";
            plane0.Status = PlaneStatus.Landing;
            plane0.UpdateType = PlaneUpdateType.UpdateExistingPlane;
            plane0.PlaneFlight = new Flight();
            plane0.PlaneFlight.FlightId = 0;
            plane0.PlaneFlight.FlightName = testFlightNames[0];
            plane0.PlaneFlight.IsDeparture = false;
            plane0.Name = testFlightNames[0];
            testPlanes.Add(plane0);

            Scenario newScenario = new Scenario(0);
            newScenario.ScenarioPlanes.AddRange(testPlanes);

            _airportScenarios.Add(newScenario);

            // Scenario 1
            // Plane 0 at beginning of 24R, ASA222, Boeing 737-900
            // Plane 1 at beginning of 24L, COA503, Boeing 777
            testPlanes = new List<Plane>();
            plane0 = new Plane();
            plane0.PlaneId = 0;
            plane0.Mass = 65000;
            plane0.TakeoffThrust = 174000;
            plane0.TakeoffSpeed = 67;
            plane0.ReverseThrust = -87000;
            plane0.Heading = 265;
            plane0.Location = initialPlaneLocations["24R"];
            plane0.PositionVector = initialPlanePositionVectors["24R"];
            plane0.CurrentEdge = initialPlaneEdgeLocations["24R"];
            plane0.PreviousMapNode = initialPreviousMapNodes["24R"];
            plane0.DestinationGate = "D10";
            plane0.Status = PlaneStatus.ClearedForLanding;
            plane0.UpdateType = PlaneUpdateType.UpdateExistingPlane;
            plane0.PlaneFlight = new Flight();
            plane0.PlaneFlight.FlightId = 0;
            plane0.PlaneFlight.FlightName = testFlightNames[0];
            plane0.PlaneFlight.IsDeparture = false;
            plane0.Name = testFlightNames[0];
            testPlanes.Add(plane0);

            plane1 = new Plane();
            plane1.PlaneId = 1;
            plane1.Mass = 250000;
            plane1.TakeoffThrust = 180000;
            plane1.TakeoffSpeed = 80;
            plane1.ReverseThrust = -180000;
            plane1.Heading = 265;
            plane1.Location = initialPlaneLocations["24L"];
            plane1.PositionVector = initialPlanePositionVectors["24L"];
            plane1.CurrentEdge = initialPlaneEdgeLocations["24L"];
            plane1.PreviousMapNode = initialPreviousMapNodes["24L"];
            plane1.Status = PlaneStatus.LinedUpAndWaiting;
            plane1.UpdateType = PlaneUpdateType.UpdateExistingPlane;
            plane1.PlaneFlight = new Flight();
            plane1.PlaneFlight.FlightId = 1;
            plane1.PlaneFlight.FlightName = testFlightNames[1];
            plane1.PlaneFlight.IsDeparture = true;
            plane1.Name = testFlightNames[1];
            testPlanes.Add(plane1);

            newScenario = new Scenario(1);
            newScenario.ScenarioPlanes.AddRange(testPlanes);

            _airportScenarios.Add(newScenario);

            // Scenario 2
            // Plane 0 at C10
            testPlanes = new List<Plane>();
            plane0 = new Plane();
            plane0.PlaneId = 0;
            plane0.Heading = 220;
            plane0.Location = initialPlaneLocations["C10"];
            plane0.PositionVector = initialPlanePositionVectors["C10"];
            plane0.CurrentEdge = initialPlaneEdgeLocations["C10"];
            plane0.PreviousMapNode = initialPreviousMapNodes["C10"];
            plane0.UpdateType = PlaneUpdateType.UpdateExistingPlane;
            plane0.PlaneFlight = new Flight();
            plane0.PlaneFlight.FlightId = 0;
            plane0.PlaneFlight.FlightName = testFlightNames[2];
            plane0.Name = testFlightNames[2];
            testPlanes.Add(plane0);

            newScenario = new Scenario(2);
            newScenario.ScenarioPlanes.AddRange(testPlanes);

            _airportScenarios.Add(newScenario);

            // Scenario 3
            // Plane 0 on 25R
            // Plane 1 at beginning of 25L
            testPlanes = new List<Plane>();
            plane0 = new Plane();
            plane0.PlaneId = 0;
            plane0.Heading = 220;
            plane0.Location = initialPlaneLocations["25R"];
            plane0.PositionVector = initialPlanePositionVectors["25R"];
            plane0.CurrentEdge = initialPlaneEdgeLocations["25R"];
            plane0.PreviousMapNode = initialPreviousMapNodes["25R"];
            plane0.UpdateType = PlaneUpdateType.UpdateExistingPlane;
            plane0.PlaneFlight = new Flight();
            plane0.PlaneFlight.FlightId = 0;
            plane0.PlaneFlight.FlightName = testFlightNames[0];
            plane0.Name = testFlightNames[0];
            testPlanes.Add(plane0);

            plane1 = new Plane();
            plane1.PlaneId = 1;
            plane1.Heading = 220;
            plane1.Location = initialPlaneLocations["25L"];
            plane1.PositionVector = initialPlanePositionVectors["25L"];
            plane1.CurrentEdge = initialPlaneEdgeLocations["25L"];
            plane1.PreviousMapNode = initialPreviousMapNodes["25L"];
            plane1.Status = PlaneStatus.LinedUpAndWaiting;
            plane1.UpdateType = PlaneUpdateType.UpdateExistingPlane;
            plane1.PlaneFlight = new Flight();
            plane1.PlaneFlight.FlightId = 1;
            plane1.PlaneFlight.FlightName = testFlightNames[1];
            plane1.Name = testFlightNames[1];
            testPlanes.Add(plane1);

            newScenario = new Scenario(3);
            newScenario.ScenarioPlanes.AddRange(testPlanes);

            _airportScenarios.Add(newScenario);
        }

        public double MapScaleFactor
        {
            get
            {
                return _mapScaleFactor;
            }
        }

        public Map AirportMap
        {
            get
            {
                return _airportMap;
            }

            set
            {
                _airportMap = value;
            }
        }

        public List<Node> AirportNodes
        {
            get
            {
                return _airportNodes;
            }

            set
            {
                _airportNodes = value;
            }
        }

        public List<Edge> AirportEdges
        {
            get
            {
                return _airportEdges;
            }

            set
            {
                _airportEdges = value;
            }
        }

        public List<Plane> ActivePlanes
        {
            get
            {
                return _activePlanes;
            }

            set
            {
                _activePlanes = value;
            }
        }

        public List<Flight> ActiveFlights
        {
            get
            {
                return _activeFlights;
            }

            set
            {
                _activeFlights = value;
            }
        }

        public void RegisterForFlightUpdates()
        {
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();                       
            if (!_clients.ContainsKey(c))
            {
                lock (_clients)
                {
                    _clients.Add(c, c);
                }
            }
        }

        public void UnregisterForFlightUpdates()
        {                       
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
            if (_clients.ContainsKey(c))
            {
                lock (_clients)
                {
                    _clients.Remove(c);
                }
            }
        }

        public void RegisterForPlaneUpdates()
        {
            Debug.WriteLine("*** RegisterForPlaneUpdates ***");

            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
            if (!_planeUpdateClients.ContainsKey(c))
            {
                lock (_planeUpdateClients)
                {
                    _planeUpdateClients.Add(c, c);
                }
            }
        }

        public void UnregisterForPlaneUpdates()
        {
            Debug.WriteLine("*** UnregisterForPlaneUpdates ***");
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
            if (_planeUpdateClients.ContainsKey(c))
            {
                lock (_planeUpdateClients)
                {
                    _planeUpdateClients.Remove(c);
                }
            }
        }

        public void RegisterForIncursionDetectedUpdates()
        {
            Debug.WriteLine("*** RegisterForIncursionDetectedUpdates ***");

            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
            if (!_incursionDetectedClients.ContainsKey(c))
            {
                lock (_incursionDetectedClients)
                {
                    _incursionDetectedClients.Add(c, c);
                }
            }
        }

        public void UnregisterForIncursionDetectedUpdates()
        {
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
            if (_incursionDetectedClients.ContainsKey(c))
            {
                lock (_incursionDetectedClients)
                {
                    _incursionDetectedClients.Remove(c);
                }
            }
        }

        // Clients call this function at startup to get the airport layout
        public void GetAirportLayout()
        {
            IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
            /*
            if (_airportMap == null)
            {
                // Load airport map from XML file
                _airportMap = LoadAirportMapFromFile(Path.GetFullPath("airport.xml"));
            }
            */
            c.IncomingAirportLayout(new Map((Map)_airportMap));
        }

        public Map GetAirportLayoutReqestReply()
        {
            /*
            if (_airportMap == null)
            {
                // Load airport map from XML file
                _airportMap = LoadAirportMapFromFile(Path.GetFullPath("airport.xml"));
            }
            */
            return new Map((Map)_airportMap);
        }

        public void GetCurrentPlaneList()
        {
            GetCurrentPlaneList(OperationContext.Current.GetCallbackChannel<IAirportCallback>());
        }

        public void GetCurrentPlaneList(IAirportCallback c)
        {
            try
            {
                lock (_activePlanesLock)
                {
                    c.IncomingCurrentPlaneList(_activePlanes);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("AirportService: {0}", ex.Message);
            }
        }

        public void GetSuggestion(Plane updatedPlane, Node destinationNode, bool returnMitigation)
        {
            // Internal method that sends a canned suggestion
            GetSuggestion(OperationContext.Current.GetCallbackChannel<IAirportCallback>(), updatedPlane, destinationNode, returnMitigation);            
        }

        // For unit testing
        public void GetSuggestion(IAirportCallback c, Plane updatedPlane, Node destinationNode, bool returnMitigation)
        {
            new Thread(new ThreadStart(
                delegate()
                {
                    Debug.WriteLine("* AirportService: GetSuggestion *");
                    try
                    {
                        Plane actualPlane;
                        lock (_activePlanesLock)
                        {
                            actualPlane = (from plane in _activePlanes
                                            where plane.Equals(updatedPlane)
                                            select plane).FirstOrDefault();
                        }

                        if (actualPlane == null)
                        {
                            throw (new Exception("Plane not found."));
                        }
                        
                        Route pendingRoute;

                        pendingRoute = _suggestionEngine.GetSuggestion(actualPlane, destinationNode, returnMitigation);

                        // Send suggested route to the client that requested it
                        c.IncomingSuggestion(pendingRoute, returnMitigation);
                        
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("AirportService.GetSuggestion: {0}", ex.Message);
                    }
                }))
            {
                IsBackground = true,
                Name = "Get Suggestion Thread"
            }
            .Start();
            
        }

        public void AcceptRoute(Route acceptedRoute)
        {
            Debug.WriteLine("* AirportService: AcceptRoute - RoutePlane = " + acceptedRoute.RoutePlane.Name + " *");

            try
            {
                // TODO Check that suggestion is valid
                if (acceptedRoute == null)
                {
                    throw (new ArgumentNullException("acceptedRoute"));
                }

                if (acceptedRoute.RoutePlane == null)
                {
                    throw (new ArgumentException("Route must have non-null RoutePlane property", "acceptedRoute"));
                }

                Plane foundPlane = null;
                lock (_activePlanesLock)
                {
                    foreach (Plane plane in _activePlanes)
                    {
                        if (plane.Equals(acceptedRoute.RoutePlane))
                        {
                            foundPlane = plane;
                            break;
                        }
                    }
                }

                // Set foundPlane's CurrentRoute to the acceptedRoute
                foundPlane.CurrentRoute = acceptedRoute;
                // Set foundPlane's destination node to the acceptedRoute.RoutePlane's DestinationNode
                foundPlane.DestinationNode = acceptedRoute.RoutePlane.DestinationNode;

                if (foundPlane.CurrentRoute.RouteNodes.Count > 0)
                {                    
                    foundPlane.PreviousRouteNodeIndex = 0;
                    // Set plane's NextRouteNodeIndex to 0, for the first RouteNode
                    foundPlane.NextRouteNodeIndex = 1;                    
                }
                else
                {
                    foundPlane.PreviousRouteNodeIndex = -1;
                    foundPlane.NextRouteNodeIndex = -1;
                }
                // Set plane's UpdateType to UpdateExistingPlane
                foundPlane.UpdateType = PlaneUpdateType.UpdateExistingPlane;

                // Fork a new thread and inform all clients about accepted Route 
                new Thread(new ThreadStart(
                    delegate()
                    {
                        lock (_planeUpdateClients)
                        {
                            if (_planeUpdateClients.Count > 0)
                            {
                                foreach (IAirportCallback client in _planeUpdateClients.Keys)
                                {
                                    client.UpdatePlane(foundPlane);
                                }
                            }
                        }
                    }
                    ))
                {
                    IsBackground = true,
                    Name = "AcceptRoute Plane Update Thread"
                }
                .Start();
            }
            catch (Exception ex)
            {
                throw (ex);
            }
        }

        public bool CancelRoute(Plane planeRouteToCancel)
        {
            Plane foundPlane = null;

            lock (_activePlanesLock)
            {
                foundPlane = (from plane in _activePlanes
                              where plane == planeRouteToCancel
                              select plane).FirstOrDefault();
                /*
                for (int i = 0; i < _activePlanes.Count; i++)
                {
                    if (_activePlanes[i] == planeRouteToCancel)
                    {
                        foundPlane = _activePlanes[i];
                    }
                }
                */
            }

            // If the plane was not found, return false
            if (foundPlane == null)
            {
                return false;
            }

            // If the plane was found, clear its Route
            //foundPlane.CurrentRoute = null;
            foundPlane.CurrentRoute = new Route();

            // Set foundPlane's UpdateType to Update an existing plane
            foundPlane.UpdateType = PlaneUpdateType.UpdateExistingPlane;

            // Fork a new thread and inform all clients about cancelled Route 
            new Thread(new ThreadStart(
                delegate()
                {
                    lock (_planeUpdateClients)
                    {
                        if (_planeUpdateClients.Count > 0)
                        {
                            foreach (IAirportCallback client in _planeUpdateClients.Keys)
                            {
                                client.UpdatePlane(foundPlane);
                            }
                        }
                    }
                }
                ))
            {
                IsBackground = true,
                Name = "CancelledRouteUpdateThread"
            }
            .Start();

            return true;
        }

        public void ClearPlaneForTakeoff(Plane plane, string runway)
        {
            try
            {
                if (plane == null)
                {
                    throw (new NullReferenceException("Plane cannot be null."));
                }
                if (runway == null)
                {
                    throw (new NullReferenceException("Runway string cannot be null."));
                }

                Debug.WriteLine(" *** ClearPlaneForTakeoff *** ");
                Debug.WriteLine("Plane: " + plane.PlaneFlight.FlightName + " Runway: " + runway);

                string oppositeRunway = "";

                switch (runway)
                {
                    case "24L":
                        oppositeRunway = "6R";
                        break;
                    case "24R":
                        oppositeRunway = "6L";
                        break;
                    case "25L":
                        oppositeRunway = "7R";
                        break;
                    case "25R":
                        oppositeRunway = "7L";
                        break;
                }

                Plane actualPlane;
                Route runwayRoute;
                lock (_activePlanesLock)
                {
                    actualPlane = (from p in _activePlanes
                                   where p.Equals(plane)
                                   select p).FirstOrDefault();

                    // Calculate shortest path for runway
                    runwayRoute = _suggestionEngine.GetSuggestion(actualPlane, _surfaceNameToNode[oppositeRunway], false);

                    //actualPlane.SetTakingOff();
                    actualPlane.Status = PlaneStatus.ClearedForTakeoff;

                    // Accept calculated Route
                    //AcceptRoute(runwayRoute);

                    IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
                    // Send suggested route to the client that requested it
                    c.IncomingSuggestion(runwayRoute, false);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("ClearPlaneForTakeoff: " + ex.Message);
            }
        }

        public void ClearPlaneForLanding(Plane plane, string runway)
        {
            try
            {
                if (plane == null)
                {
                    throw (new NullReferenceException("Plane cannot be null."));
                }
                if (runway == null)
                {
                    throw (new NullReferenceException("Runway string cannot be null."));
                }

                Debug.WriteLine(" *** ClearPlaneForLanding *** ");
                Debug.WriteLine("Plane: " + plane.PlaneFlight.FlightName + " Runway: " + runway);

                string oppositeRunway = "";

                switch (runway)
                {
                    case "24L":
                        oppositeRunway = "6R";
                        break;
                    case "24R":
                        oppositeRunway = "6L";
                        break;
                    case "25L":
                        oppositeRunway = "7R";
                        break;
                    case "25R":
                        oppositeRunway = "7L";
                        break;
                }

                Plane actualPlane;
                Route runwayRoute;
                lock (_activePlanesLock)
                {
                    actualPlane = (from p in _activePlanes
                                   where p.Equals(plane)
                                   select p).FirstOrDefault();

                    // Calculate shortest path for runway
                    runwayRoute = _suggestionEngine.GetSuggestion(actualPlane, _surfaceNameToNode[oppositeRunway], false);

                    actualPlane.Status = PlaneStatus.ClearedForLanding;

                    // Accept calculated Route
                    //AcceptRoute(runwayRoute);

                    IAirportCallback c = OperationContext.Current.GetCallbackChannel<IAirportCallback>();
                    // Send suggested route to the client that requested it
                    c.IncomingSuggestion(runwayRoute, false);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine("ClearPlaneForLanding: " + ex.Message);
            }
        }

        public void CalculateAndAcceptSuggestion(Plane updatedPlane, Node destinationNode)
        {

        }

        public void GetScenarioList()
        {
            GetScenarioList(OperationContext.Current.GetCallbackChannel<IAirportCallback>());
        }

        public void GetScenarioList(IAirportCallback c)
        {
            try
            {
                c.IncomingScenarioList(_airportScenarios);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("AirportService: {0}", ex.Message);
            }
        }

        public void LoadScenario(int scenarioNumber)
        {
            try
            {
                if (scenarioNumber < 0 || scenarioNumber >= _airportScenarios.Count)
                {
                    throw (new IndexOutOfRangeException("Scenario Number Out of Range"));
                }
                lock (_activePlanesLock)
                {
                    _activePlanes = _airportScenarios[scenarioNumber].ScenarioPlanes;
                }
                // Spawn thread to update clients with the new ActivePlaneList
                new Thread(new ThreadStart(
                delegate()
                {
                    lock (_planeUpdateClients)
                    {
                        if (_planeUpdateClients.Count > 0)
                        {
                            lock (_activePlanesLock)
                            {
                                foreach (IAirportCallback client in _planeUpdateClients.Keys)
                                {
                                    client.IncomingCurrentPlaneList(_activePlanes);
                                }
                            }
                        }
                    }
                }
                ))
                {
                    IsBackground = true,
                    Name = "LoadScenario Update Thread"
                }
            .Start();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("LoadScenario: " + ex.Message);
            }
        }

        public void UpdatePlanePosition(Plane updatedPlane)
        {
            //Debug.WriteLine("* AirportService: UpdatePlanePosition *");

            updatedPlane.UpdateType = PlaneUpdateType.UpdateExistingPlane;

            // Fork a new thread and inform all clients about accepted Route 
            new Thread(new ThreadStart(
                delegate()
                {
                    lock (_planeUpdateClients)
                    {
                        if (_planeUpdateClients.Count > 0)
                        {
                            foreach (IAirportCallback client in _planeUpdateClients.Keys)
                            {
                                client.UpdatePlane(updatedPlane);
                            }
                        }
                    }
                }
                ))
            {
                IsBackground = true,
                Name = "UpdatePlanePosition Plane Update Thread"
            }
            .Start();
        }

        public void UpdateAllPlanePositions()
        {
            //Debug.WriteLine("* AirportService: UpdateAllPlanePositions *");

            //updatedPlane.UpdateType = PlaneUpdateType.UpdateExistingPlane;
            // Fork a new thread and inform all clients about accepted Route 
            new Thread(new ThreadStart(
                delegate()
                {
                    lock (_planeUpdateClients)
                    {
                        if (_planeUpdateClients.Count > 0)
                        {
                            lock (_activePlanesLock)
                            {
                                for (int i = 0; i < _activePlanes.Count; i++)
                                {
                                    foreach (IAirportCallback client in _planeUpdateClients.Keys)
                                    {
                                        client.UpdatePlane(_activePlanes[i]);
                                    }

                                    // If the current Plane is New, we have told all clients to Add 
                                    // the new Plane, so change its update type to UpdateExistingPlane
                                    if (_activePlanes[i].UpdateType == PlaneUpdateType.AddNewPlane)
                                    {
                                        _activePlanes[i].UpdateType = PlaneUpdateType.UpdateExistingPlane;
                                    }
                                }
                            }
                        }
                    }
                }
                ))
            {
                IsBackground = true,
                Name = "UpdatePlanePosition Plane Update Thread"
            }
            .Start();
        }

        /// <summary>
        /// Verifies whether the given List of strings constitutes a legal Route.
        /// </summary>
        /// <param name="routeStrings"></param>
        /// <param name="overrideAccessibleEdges">If true, the method will assume that all Edges are accessible from any neighboring Edge,
        /// ignoring the AccessibleNeighbors List. This should only be true for emergencies.</param>
        public void VerifyRouteString(List<string> routeStrings, bool overrideAccessibleEdges)
        {
            VerifyRouteString(OperationContext.Current.GetCallbackChannel<IAirportCallback>(), routeStrings, overrideAccessibleEdges);
        }

        public void VerifyRouteString(IAirportCallback c, List<string> routeStrings, bool overrideAccessibleEdges)
        {
            Debug.WriteLine("*** VerifyRouteString ***");

            //List<KeyValuePair<Node, Edge>> pathFromStrings = _suggestionEngine.GetPathFromStrings(routeStrings, overrideAccessibleEdges);
            Route routeFromStrings = _suggestionEngine.GetPathFromStrings(routeStrings, overrideAccessibleEdges);
           
            if (routeFromStrings == null)
            {
                // TODO Figure out a way to make this error more specific
                c.RouteStringVerificationFailed(routeStrings);
            }
            else
            {
                // Call callback to pass the Route back
                c.IncomingSuggestion(routeFromStrings, false);
            }
        }

        public void IncursionDetectorThread()
        {            
            List<IAirportCallback> bad = new List<IAirportCallback>();
            int count = 0;            
            PlaneComparer planeComparer = new PlaneComparer();
            IncursionPlanesInvolvedEqualityComparer incursionComparer = new IncursionPlanesInvolvedEqualityComparer();

            while (true)
            {
                List<Incursion> detectedIncursions = new List<Incursion>(); // All detected Incursions
                List<Incursion> incursionsToRemove = new List<Incursion>(); // Resolved Incursions
                List<Incursion> newIncursions = new List<Incursion>(); // New Incursions

                Thread.Sleep(1000);
                //Debug.WriteLine("\n*** IncursionDetectorThread ***\n");                

                lock (_activePlanesLock)
                {
                    // Query _activePlanes for a collection of currently active Routes
                    List<Route> activeRoutesList = (from activePlane in _activePlanes
                                       where activePlane.CurrentRoute != null 
                                       select activePlane.CurrentRoute).ToList();

                    bool anIncursionHasBeenFound = false;
                    foreach (Route route in activeRoutesList)
                    {
                        Incursion foundIncursion = _suggestionEngine.CheckForIncursions(route);

                        if (foundIncursion != null)
                        {
                            foundIncursion.PlanesInvolved.Sort(planeComparer);

                            List<Mitigation> foundMitigations = new List<Mitigation>();
                            // Determine a mitigation
                            foreach (Route incursionRoute in foundIncursion.IncursionRoutes)
                            {
                                //foundIncursion.MitigatedRoutes.Add(_suggestionEngine.SuggestMitigation(incursionRoute.StartTime, incursionRoute));
                                foundMitigations.Add(_suggestionEngine.SuggestMitigation(incursionRoute.StartTime, incursionRoute));
                                //foundIncursion.Mitigations.Add(mitigation);
                            }

                            foundIncursion.Mitigations = foundMitigations;

                            detectedIncursions.Add(foundIncursion);
                            anIncursionHasBeenFound = true;
                        }
                    }

                    if (anIncursionHasBeenFound)
                    {
                        Debug.WriteLine("*** IncursionDetectorThread: Found Incursion ***");
                    }

                    // Get all incursions in _incursionList that are not in detectedIncursions, these will be removed from _incursionList 
                    List<Incursion> notInDetectedIncursions = _incursionList.Except(detectedIncursions, incursionComparer).ToList();
                    incursionsToRemove.AddRange(notInDetectedIncursions);
                    /*
                    foreach (Incursion resolvedIncursion in notInDetectedIncursions)
                    {
                        incursionsToRemove.Add(resolvedIncursion);                        
                    }
                    */

                    foreach (Incursion incursionToRemove in incursionsToRemove)
                    {
                        _incursionList.Remove(incursionToRemove);
                    }

                    // Get all distinct values in detectedIncursions
                    List<Incursion> distinctDetectedIncursions = detectedIncursions.Distinct(incursionComparer).ToList();

                    foreach (Incursion distinctIncursion in distinctDetectedIncursions)
                    {
                        // Get all duplicate Incursions
                        List<Incursion> overlappingIncursions = (from detectedIncursion in detectedIncursions
                                                                where incursionComparer.Equals(distinctIncursion, detectedIncursion)
                                                                select detectedIncursion).ToList();

                        Incursion earliestIncursion = new Incursion();
                        earliestIncursion.IncursionRouteNode.PredictedTOA = DateTime.MaxValue;

                        // Get Incursion with the earliest PredictedTOA
                        foreach (Incursion incursion in overlappingIncursions)
                        {
                            if (incursion.IncursionRouteNode.PredictedTOA < earliestIncursion.IncursionRouteNode.PredictedTOA)
                            {
                                earliestIncursion = incursion;
                            }
                        }

                        int foundIndex = -1;
                        for (int i = 0; i < _incursionList.Count; i++)
                        {
                            if (incursionComparer.Equals(earliestIncursion, _incursionList[i]))
                            {
                                foundIndex = i;
                                break;
                            }
                        }
                        // Check if this incursion is not in _incursionList
                        if (foundIndex == -1)
                        {
                            earliestIncursion.IncusionId = _currentIncursionId++;
                            // If it is not yet in _incursionList, add it
                            _incursionList.Add(earliestIncursion);
                            newIncursions.Add(earliestIncursion);                            
                        }
                        else
                        {
                            if (earliestIncursion.IncursionRouteNode.PredictedTOA < _incursionList[foundIndex].IncursionRouteNode.PredictedTOA)
                            {
                                earliestIncursion.IncusionId = _currentIncursionId++;
                                incursionsToRemove.Add(_incursionList[foundIndex]);
                                newIncursions.Add(earliestIncursion);
                                _incursionList.RemoveAt(foundIndex);
                            }
                        }
                    }
                }

                lock (_incursionDetectedClients)
                {
                    if (_incursionDetectedClients.Count > 0)
                    {                        
                        bad.Clear();
                        
                        foreach (IAirportCallback client in _incursionDetectedClients.Keys)
                        {
                            try
                            {
                                // Inform client of newly detected Incursions
                                foreach (Incursion incursion in newIncursions)
                                {
                                    Debug.WriteLine("IncursionDetectorThread: Newly-Detected Incursions");
                                    /*
                                    object dto = incursion;
                                    DataContractSerializer ser = new DataContractSerializer(typeof(Incursion));
                                    FileStream writer = new FileStream(@"C:\Temp\" + dto.GetType().Name + "_stackoverflow.xml", FileMode.Create);
                                    ser.WriteObject(writer, dto);
                                    writer.Close();
                                    */
                                    client.AddNewPotentialIncursion(incursion);
                                }

                                // Inform client of resolved incursions
                                //incursionsToRemove.ForEach(incursion => client.RemovePotentialIncursion(incursion));
                                foreach (Incursion incursion in incursionsToRemove)
                                {
                                    Debug.WriteLine("IncursionDetectorThread: Resolved Incursions");
                                    client.RemovePotentialIncursion(incursion);
                                }
                            }
                            
                            catch (Exception)
                            {
                                Debug.WriteLine("AirportService: Calling client.PotentialIncursionDetected failed");
                                bad.Add(client);
                            }
                             
                        }
                        count++;
                        bad.ForEach(bc => _incursionDetectedClients.Remove(bc));
                    }
                }
            }
        }

        public void pumpUpdates()
        {
            Random w = new Random();

            List<IAirportCallback> bad = new List<IAirportCallback>();
            int count = 0;
            Node[] initialPlaneLocations = new Node[4];

            initialPlaneLocations[0] = new Node(-1, 527, 36);
            initialPlaneLocations[1] = new Node(-1, 532, 71);
            initialPlaneLocations[2] = new Node(-1, 818, 306);
            initialPlaneLocations[3] = new Node(-1, 863, 261);

            // Create a test Route
            Route testRoute = new Route();
            List<RouteNode> testRouteNodes = new List<RouteNode>();
            testRouteNodes.Add(new RouteNode(0,90,91));
            testRouteNodes.Add(new RouteNode(1,185,79));
            testRouteNodes.Add(new RouteNode(2,280,67));
            testRouteNodes.Add(new RouteNode(3,298,65));
            testRouteNodes.Add(new RouteNode(4,392,52));
            testRouteNodes.Add(new RouteNode(5, 527, 36));
            testRoute.RouteNodes = testRouteNodes;

            while (true)
            {
                Thread.Sleep(w.Next(10000));

                lock (_planeUpdateClients)
                {
                    if (_planeUpdateClients.Count > 0)
                    {
                        // This is for testing
                        Flight testFlight = new Flight();
                        string airline = ((count % 2) == 0) ? "SWA" : "VA";
                        testFlight.AirlineCode = airline;
                        testFlight.FlightNumber = count;
                        testFlight.FlightName = airline + count;

                        Plane testPlane = new Plane();
                        testPlane.PlaneId = count;
                        testPlane.Location = initialPlaneLocations[count % 4];
                        testPlane.Heading = 220;
                        //testPlane.CurrentRoute = testRoute;
                        testFlight.FlightId = count;
                        testPlane.PlaneFlight = testFlight;
                        testPlane.UpdateType = PlaneUpdateType.AddNewPlane;

                        // Add new Flight to local collection
                        _activeFlights.Add(testFlight);
                        // Add new Plane to local collection
                        _activePlanes.Add(testPlane);

                        bad.Clear();
                        /*
                        foreach (IAirportCallback client in _clients.Keys)
                        {
                            try
                            {                                
                                client.newFlightUpdate(testFlight);                                
                            }
                            catch (Exception)
                            {
                                Console.WriteLine("AirportService: Calling client.newFlightUpdate failed");
                                bad.Add(client);
                            }
                        }
                        count++;
                        bad.ForEach(bc => _clients.Remove(bc));
                         * */
                        foreach (IAirportCallback client in _planeUpdateClients.Keys)
                        {
                            try
                            {                                
                                client.UpdatePlane(testPlane);
                            }
                            catch (Exception)
                            {
                                Debug.WriteLine("AirportService: Calling client.UpdatePlane failed");
                                bad.Add(client);
                            }
                        }
                        count++;
                        bad.ForEach(bc => _planeUpdateClients.Remove(bc));
                    }
                }
            }
        }

        private static Map LoadAirportMapFromFile(string fileName)
        {
            Tuple<double, double> ORIGINAL_MAP_DIM = new Tuple<double, double>(900, 420);
            //Tuple<double, double> SCALED_MAP_DIM = new Tuple<double, double>(1280, 800);
            Tuple<double, double> SCALED_MAP_DIM = new Tuple<double, double>(1056, 493);
            double SCALE_X = SCALED_MAP_DIM.Item1 / ORIGINAL_MAP_DIM.Item1;
            double SCALE_Y = SCALED_MAP_DIM.Item2 / ORIGINAL_MAP_DIM.Item2;

            ObservableCollection<Runway> runways = new ObservableCollection<Runway>();
            ObservableCollection<Taxiway> taxiways = new ObservableCollection<Taxiway>();

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(fileName);
            XmlElement xmlRoot = xmlDoc.DocumentElement;
            XmlNodeList xmlRunways = xmlRoot.SelectNodes("/airport/runways/runway");
            XmlNodeList xmlTaxiways = xmlRoot.SelectNodes("/airport/taxiways/taxiway");

            /*
             * <runway direction1="" direction2="">
             *      <maxWeight>
             *      <width>
             *      <arrivals>
             *      <departures>
             *      <labelLocation>
             *      <nodes>
            */
            foreach (XmlNode xmlRunway in xmlRunways)
            {
                Runway newRunway = new Runway();
                newRunway.Direction1 = xmlRunway.Attributes["direction1"].Value;
                newRunway.Direction2 = xmlRunway.Attributes["direction2"].Value;
                newRunway.MaxWeight = Convert.ToInt32(xmlRunway.ChildNodes[0].InnerText);
                newRunway.Width = Convert.ToInt32(xmlRunway.ChildNodes[1].InnerText);

                // Set RunwayStatus
                if (xmlRunway.ChildNodes[2].InnerText == "1")
                {
                    // If both Arrival and Departure are true, set RunwayStatus to ARRIVE_DEPART
                    if (xmlRunway.ChildNodes[3].InnerText == "1")
                    {
                        newRunway.CurrentRunwayStatus = RunwayStatus.ARRIVE_DEPART;
                    }
                    else
                    {
                        newRunway.CurrentRunwayStatus = RunwayStatus.ARRIVE;
                    }
                }
                else
                {
                    newRunway.CurrentRunwayStatus = RunwayStatus.DEPART;
                }
                //newRunway.Arrivals = (xmlRunway.ChildNodes[2].InnerText == "0" ? false : true);
                //newRunway.Departures = (xmlRunway.ChildNodes[3].InnerText == "0" ? false : true);

                newRunway.SetLabelLocation(xmlRunway.ChildNodes[4].InnerText, SCALE_X, SCALE_Y);

                foreach (XmlNode xmlRunwayNode in xmlRunway.ChildNodes[5].ChildNodes)
                {
                    newRunway.AddNode(xmlRunwayNode.InnerText, SCALE_X, SCALE_Y);
                }
                /*
                // Set Runway Endpoints
                newRunway.Endpoint1 = newRunway.Nodes.First();
                newRunway.Endpoint2 = newRunway.Nodes.Last();
                */
                runways.Add(newRunway);
            }

            /*
             * <taxiway name="">
             *      <maxWeight>
             *      <width>
             *      <nodes>
            */
            foreach (XmlNode xmlTaxiway in xmlTaxiways)
            {
                Taxiway newTaxiway = new Taxiway();
                newTaxiway.Name = xmlTaxiway.Attributes["name"].Value;
                newTaxiway.MaxWeight = Convert.ToInt32(xmlTaxiway.ChildNodes[0].InnerText);
                newTaxiway.Width = Convert.ToInt32(xmlTaxiway.ChildNodes[1].InnerText);

                foreach (XmlNode xmlTaxiwayNode in xmlTaxiway.ChildNodes[2].ChildNodes)
                {
                    newTaxiway.AddNode(xmlTaxiwayNode.InnerText, SCALE_X, SCALE_Y);
                }
                /*
                // Set Taxiway Endpoints
                newTaxiway.Endpoint1 = newTaxiway.Nodes.First();
                newTaxiway.Endpoint2 = newTaxiway.Nodes.Last();
                */
                taxiways.Add(newTaxiway);
            }

            /*
            foreach(Runway runway in runways) {
                Console.WriteLine(runway);
            }

            foreach(Taxiway taxiway in taxiways) {
                Console.WriteLine(taxiway);
            }

            Console.ReadLine();
            */

            return new Map(runways, taxiways, new ObservableCollection<Gate>());
        }

        private void InitializeAirportNodesAndEdges()
        {
            try
            {
                if (_airportMap == null)
                {
                    _airportMap = new Map();
                }

                Dictionary<string, List<Edge>> edges = new Dictionary<string, List<Edge>>();
                // Query airportEdges for all edges that are runways
                var groupedEdgesQuery = from edge in _airportEdges
                                        group edge by edge.Name into edgeGroup
                                        select edgeGroup;
                // Put all edges into Dictionary grouping them by edge
                foreach (var edgeGroup in groupedEdgesQuery)
                {
                    edges[edgeGroup.Key] = edgeGroup.ToList();
                }

                foreach (string edgeName in edges.Keys)
                {
                    //foreach (string edgeName in _runwayNames)
                    if (_runwayNames.Contains(edgeName, StringComparer.CurrentCultureIgnoreCase))
                    {
                        string[] directions = edgeName.Split('/');

                        Runway runway = new Runway();
                        runway.Direction1 = directions[0];
                        runway.Direction2 = directions[1];
                        runway.Name = edgeName;

                        var endpoint1Query = from edge in edges[edgeName]
                                             select edge.Endpoint1;
                        var endpoint2Query = from edge in edges[edgeName]
                                             select edge.Endpoint2;
                        var runwayNodesQuery = endpoint1Query.Union(endpoint2Query);

                        // To avoid XML serialization errors, set Neighbors to an empty List
                        List<Node> runwayNodesList = runwayNodesQuery.ToList();
                        runwayNodesList.ForEach(delegate(Node node)
                        {
                            node.Neighbors = new List<Node>();
                        });

                        runway.Nodes = runwayNodesList;
                        runway.Edges = edges[edgeName];
                        _airportMap.Runways.Add(runway);
                    }
                    else
                    {
                        Taxiway taxiway = new Taxiway();
                        taxiway.Name = edgeName;

                        var endpoint1Query = from edge in edges[edgeName]
                                             select edge.Endpoint1;
                        var endpoint2Query = from edge in edges[edgeName]
                                             select edge.Endpoint2;
                        var taxiwayNodesQuery = endpoint1Query.Union(endpoint2Query);

                        // To avoid XML serialization errors, set Neighbors to an empty List
                        List<Node> taxiwayNodesList = taxiwayNodesQuery.ToList();
                        taxiwayNodesList.ForEach(delegate(Node node)
                        {
                            node.Neighbors = new List<Node>();
                        });

                        taxiway.Nodes = taxiwayNodesList;
                        taxiway.Edges = edges[edgeName];
                        _airportMap.Taxiways.Add(taxiway);
                    }
                }
                
                Debug.WriteLine("Finished Loading Nodes and Edges");

                /*
                foreach (Runway runway in _airportMap.Runways)
                {                    
                    // Iterate through this Runway's Nodes
                    for (int i = 0; i < runway.Nodes.Count; i++)
                    {                        
                        Node workingNode = new Node();  
                        bool found = false;

                        // Check if the current Node is already in _airportNodes
                        for (int j = 0; j < _airportNodes.Count; j++)
                        {
                            // If the current Node is already in _airportNodes,
                            // set workingNode to the existing Node
                            if (runway.Nodes[i] == _airportNodes[j])
                            {
                                workingNode = _airportNodes[j];
                                found = true;
                                break;
                            }
                        }

                        // If the current Node was not found in _airportNodes,
                        // set workingNode to a copy of the current Node and
                        // add workingNode to _airportNodes
                        if (!found)
                        {
                            workingNode = new Node(runway.Nodes[i]);
                            _airportNodes.Add(workingNode);
                        }
                        
                        // Iterate over _airportNodes to find the Node preceding
                        // workingNode in the runway and add workingNode and
                        // the preceding Node to each other's Neighbors Lists
                        for (int k = 0; k < _airportNodes.Count; k++)
                        {
                            if (i > 0)
                            {
                                if (runway.Nodes[i - 1] == _airportNodes[k])
                                {                                    
                                    // Add the previous Node to workingNode's Neighbors
                                    workingNode.Neighbors.Add(_airportNodes[k]);
                                    
                                    // Add workingNode to the previous Node's Neighbors
                                    _airportNodes[k].Neighbors.Add(workingNode);
                                    break;
                                }                               
                            }                            
                        }                                                
                    }
                }
                
                foreach (Taxiway taxiway in _airportMap.Taxiways) 
                {
                    // Iterate through this Taxiway's Nodes
                    for (int i = 0; i < taxiway.Nodes.Count; i++) 
                    {
                        Node workingNode = new Node(); 
                        bool found = false;

                        // Check if the current Node is already in _airportNodes
                        for (int j = 0; j < _airportNodes.Count; j++) 
                        {
                            // If the current Node is already in _airportNodes,
                            // set workingNode to the existing Node
                            if (taxiway.Nodes[i] == _airportNodes[j])
                            {
                                workingNode = _airportNodes[j];
                                found = true;
                                break;
                            }
                        }

                        // If the current Node was not found in _airportNodes,
                        // set workingNode to a copy of the current Node and
                        // add workingNode to _airportNodes
                        if (!found)
                        {
                            workingNode = new Node(taxiway.Nodes[i]);
                            _airportNodes.Add(workingNode);
                        }

                        // Iterate over _airportNodes to find the Node preceding
                        // workingNode in the runway and add workingNode and
                        // the preceding Node to each other's Neighbors Lists
                        for (int k = 0; k < _airportNodes.Count; k++)
                        {
                            if (i > 0)
                            {
                                if (taxiway.Nodes[i - 1] == _airportNodes[k])
                                {
                                    // Add the previous Node to workingNode's Neighbors
                                    workingNode.Neighbors.Add(_airportNodes[k]);

                                    // Add workingNode to the previous Node's Neighbors
                                    _airportNodes[k].Neighbors.Add(workingNode);
                                    break;
                                }
                            }
                        }
                    }
                }

                // DEBUGGING
                foreach (Node node in _airportNodes)
                {
                    Debug.WriteLine("Node: {0}", node);
                    Debug.Write("   Neighbors: ");
                    foreach (Node neighbor in node.Neighbors)
                    {
                        Debug.Write(neighbor);
                        Debug.Write(" ");
                    }
                    Debug.Write("\n");
                }
                */
            }
            catch (Exception ex)
            {
                Debug.WriteLine("AirportService: {0}", ex.Message);
            }
        }
    }
    
}
